package com.doctcloud.hisinfo.inpbill.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.doctcloud.common.core.utils.StringUtils;
import com.doctcloud.hisinfo.histable.domain.inpbill.InpBillDetailDo;
import com.doctcloud.hisinfo.inpbill.mapper.InpBillDetailDoMapper;
import com.doctcloud.hisinfo.inpbill.request.InpBillRequest;
import com.doctcloud.hisinfo.inpbill.service.IInpBillDetailService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.doctcloud.common.core.constant.CacheConstants.*;

@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class InpBillDetailService extends ServiceImpl<InpBillDetailDoMapper, InpBillDetailDo> implements IInpBillDetailService {
    private static final String INDEX_NAME = "his_inpbilldetail";
    private static final String LOCK_KEY_PREFIX = HIS_SYNC_LOCK_PREFIX + "inpbill_pat_";
    private final RedissonClient redissonClient;

    /**
     * 保存住院费用记录
     * 使用分布式锁 + 数据库悲观锁保证并发安全
     * @param data 住院费用记录数据
     * @return 插入成功的条数
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class) // 分布式事务注解依然保留
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer saveItem(@Validated InpBillDetailDo data) {
        if (data == null) {
            log.warn("传入的住院费用记录数据为null，保存失败。");
            return 0;
        }
        String patientId = data.getPatientId();
        if (StringUtils.isBlank(patientId)) {
            log.warn("患者ID为空，保存住院费用记录失败。");
            return 0;
        }

        String lockKey = LOCK_KEY_PREFIX + patientId;
        RLock addLock = redissonClient.getLock(lockKey);
        boolean isAddLocked = false;
        Integer insertCount = 0;
        int retryCount = 0;

        try {
            while (retryCount < HIS_CACHE_WAIT_RETRIES) {
                isAddLocked = addLock.tryLock(HIS_LOCK_WAIT_SECONDS, HIS_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
                if (isAddLocked) {
                    break;
                }
                retryCount++;
                if (retryCount >= HIS_CACHE_WAIT_RETRIES) {
                    log.warn("患者ID[{}]获取分布式锁失败，已重试{}次，操作失败", patientId, HIS_CACHE_WAIT_RETRIES);
                    return 0;
                }
                log.debug("患者ID[{}]第{}次获取分布式锁失败，{}ms后重试", patientId, retryCount, HIS_CACHE_WAIT_INTERVAL_MS);
                TimeUnit.MILLISECONDS.sleep(HIS_CACHE_WAIT_INTERVAL_MS);
            }

            if (isAddLocked) {
                insertCount = doSaveWithPessimisticLock(data);
            }
            return insertCount;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("新增患者[{}]住院费用记录时线程被中断", patientId, e);
            return 0;
        } finally {
            releaseLock(addLock, isAddLocked);
        }
    }

    /**
     * 批量保存住院费用记录
     * 为每一个患者ID获取锁，然后在数据库事务中批量处理
     *
     * @param datas 住院费用记录列表
     * @return 插入成功的总条数
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer saveItems(@Validated List<InpBillDetailDo> datas) {
        if (CollectionUtils.isEmpty(datas)) {
            log.warn("传入的住院费用记录列表为空，批量保存失败。");
            return 0;
        }

        // 按 patientId 分组，确保同一患者的记录在同一个锁和事务中处理
        Map<String, List<InpBillDetailDo>> dataByPatientId = datas.stream()
                .filter(data -> StringUtils.isNotBlank(data.getPatientId()))
                .collect(Collectors.groupingBy(InpBillDetailDo::getPatientId));

        int totalInserted = 0;
        Date currentDate = new Date();

        for (Map.Entry<String, List<InpBillDetailDo>> entry : dataByPatientId.entrySet()) {
            String patientId = entry.getKey();
            List<InpBillDetailDo> patientDatas = entry.getValue();

            String lockKey = LOCK_KEY_PREFIX + patientId;
            RLock addLock = redissonClient.getLock(lockKey);
            boolean isAddLocked = false;
            int retryCount = 0;

            try {
                while (retryCount < HIS_CACHE_WAIT_RETRIES) {
                    isAddLocked = addLock.tryLock(HIS_LOCK_WAIT_SECONDS, HIS_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
                    if (isAddLocked) {
                        break;
                    }
                    retryCount++;
                    if (retryCount >= HIS_CACHE_WAIT_RETRIES) {
                        log.warn("患者ID[{}]获取分布式锁失败，已重试{}次，跳过该患者的批量保存", patientId, HIS_CACHE_WAIT_RETRIES);
                        break;
                    }
                    log.debug("患者ID[{}]第{}次获取分布式锁失败，{}ms后重试", patientId, retryCount, HIS_CACHE_WAIT_INTERVAL_MS);
                    TimeUnit.MILLISECONDS.sleep(HIS_CACHE_WAIT_INTERVAL_MS);
                }

                if (isAddLocked) {
                    totalInserted += doSaveBatchWithPessimisticLock(patientDatas, currentDate);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("批量保存患者[{}]住院费用记录时线程被中断", patientId, e);
            } finally {
                releaseLock(addLock, isAddLocked);
            }
        }

        log.info("批量保存完成，总共插入 {} 条记录", totalInserted);
        return totalInserted;
    }

    /**
     * 删除单条住院费用记录
     *
     * @param data 住院费用记录数据（至少包含主键或唯一标识）
     * @return 删除成功的条数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer delItem(InpBillDetailDo data) {
        if (data == null || data.getItemNo() == null || StringUtils.isBlank(data.getPatientId())) {
            log.warn("删除条件不足，无法删除住院费用记录。");
            return 0;
        }
        LambdaQueryWrapper<InpBillDetailDo> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(InpBillDetailDo::getPatientId, data.getPatientId())
                .eq(InpBillDetailDo::getVisitId,data.getVisitId())
                .eq(InpBillDetailDo::getItemNo, data.getItemNo());
        return baseMapper.delete(queryWrapper);
    }
    /**
     * 更新单条住院费用记录
     *
     * @param data 住院费用记录数据（至少包含主键或唯一标识）
     * @return 更新成功的条数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer updateItem(InpBillDetailDo data) {
        if (data == null || data.getItemNo() == null || StringUtils.isBlank(data.getPatientId())) {
            log.warn("更新条件不足，无法更新住院费用记录。");
            return 0;
        }
        // 确保更新时不会修改创建时间和序号
        data.setBillingDateTime(null);
        data.setItemNo(null);

        LambdaQueryWrapper<InpBillDetailDo> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(InpBillDetailDo::getPatientId, data.getPatientId())
                .eq(InpBillDetailDo::getVisitId,data.getVisitId())
                .eq(InpBillDetailDo::getItemNo, data.getItemNo());
        return baseMapper.update(data, queryWrapper);
    }
    /**
     * 批量删除住院费用记录
     *
     * @param datas 住院费用记录列表（每条记录至少包含主键或唯一标识）
     * @return 删除成功的条数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer delItems(List<InpBillDetailDo> datas) {
        if (CollectionUtils.isEmpty(datas)) {
            log.warn("传入的住院费用记录列表为空，批量删除失败。");
            return 0;
        }

        List<Map<String, Object>> deleteConditions = datas.stream()
                .filter(data -> data.getItemNo() != null && StringUtils.isNotBlank(data.getPatientId()) && data.getVisitId() != null)
                .map(data -> {
                    Map<String, Object> condition = new HashMap<>();
                    condition.put("patient_id", data.getPatientId());
                    condition.put("visit_id",data.getVisitId());
                    condition.put("item_no", data.getItemNo());
                    return condition;
                })
                .collect(Collectors.toList());

        if (deleteConditions.isEmpty()) {
            log.warn("批量删除条件不足，无法执行删除。");
            return 0;
        }
        return baseMapper.deleteBatchByPatientIdAndItemNo(deleteConditions);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = {"InpBillRequest"}, allEntries = true) // 添加 @CacheEvict 注解来清除相关的缓存。
    public Integer updateItems(List<InpBillDetailDo> datas) {
        if (CollectionUtils.isEmpty(datas)) {
            log.warn("传入的住院费用记录列表为空，批量更新失败。");
            return 0;
        }

        int totalUpdated = 0;
        for (InpBillDetailDo data : datas) {
            totalUpdated += updateItem(data);
        }
        return totalUpdated;
    }
    @Override
    @Cacheable(value = {"InpBillRequest"}, keyGenerator = "springCacheCustomKeyGenerator",cacheManager = "cacheManager10Minute")
    public List<InpBillDetailDo> listItems(InpBillRequest data) {
        return listItemsInternal(data);
    }

    @Override
    public List<InpBillDetailDo> DBListByPat(String patientId, Integer visitId) {
        List<InpBillDetailDo> resList = new ArrayList<>();
        if (StringUtils.isEmpty(patientId)) {
            log.error("传入的患者ID为空，提取信息失败。");
            return resList;
        }
        if (ObjectUtils.isEmpty(visitId)) {
            log.error("传入的住院标识为空，提取信息失败");
            return resList;
        }
        LambdaQueryWrapper<InpBillDetailDo> query = Wrappers.lambdaQuery();
        query.eq(InpBillDetailDo::getPatientId,patientId)
                .eq(InpBillDetailDo::getVisitId,visitId);
        return baseMapper.selectList(query);
    }

    @Override
    public List<InpBillDetailDo> DBListItems(InpBillRequest data) {
        return listItemsInternal(data);
    }

    /**
     * 在数据库事务中，使用悲观锁（SELECT ... FOR UPDATE）来确保序号生成的原子性
     * 此方法必须在分布式锁的保护下调用
     * @param data 住院费用记录数据
     * @return 插入成功的条数
     */
    @Transactional(rollbackFor = Exception.class) // 本地事务，确保查询和插入在同一个事务中
    public Integer doSaveWithPessimisticLock(InpBillDetailDo data) {
        log.info("开始为患者ID[{}]保存住院费用记录，进入数据库事务和悲观锁范围", data.getPatientId());

        // 1. 查询当前最大序号，并对查询结果加锁 (FOR UPDATE)
        // 这个查询会锁定 inp_bill_detail 表中 patient_id 和 visit_id 匹配的行
        Integer itemNo = baseMapper.maxItemNoForUpdate(data.getPatientId(), data.getVisitId());

        // 2. 计算新序号
        itemNo = ObjectUtils.isEmpty(itemNo) ? 1 : itemNo + 1;

        // 3. 设置新序号和当前时间
        data.setItemNo(itemNo);
        data.setBillingDateTime(new Date());

        // 4. 插入数据
        int insertCount = baseMapper.insert(data);

        log.info("患者ID[{}]已成功添加住院费用记录，序号为[{}]", data.getPatientId(), itemNo);
        return insertCount;
    }

    /**
     * 在数据库事务中，使用悲观锁为单个患者批量保存记录
     */
    @Transactional(rollbackFor = Exception.class)
    public Integer doSaveBatchWithPessimisticLock(List<InpBillDetailDo> datas, Date currentDate) {
        if (CollectionUtils.isEmpty(datas)) {
            return 0;
        }
        String patientId = datas.get(0).getPatientId();
        Integer visitId = datas.get(0).getVisitId();

        log.info("开始为患者ID[{}]批量保存住院费用记录，共 {} 条", patientId, datas.size());

        Integer maxItemNo = baseMapper.maxItemNoForUpdate(patientId, visitId);
        int nextItemNo = ObjectUtils.isEmpty(maxItemNo) ? 1 : maxItemNo + 1;

        for (InpBillDetailDo data : datas) {
            data.setItemNo(nextItemNo++);
            data.setBillingDateTime(currentDate);
        }

        int insertedCount = baseMapper.insertBatch(datas);
        log.info("患者ID[{}]批量保存成功，共插入 {} 条记录", patientId, insertedCount);
        return insertedCount;
    }

    /**
     * 释放分布式锁
     * @param lock 锁实例
     * @param isLocked 是否已获取锁
     */
    private void releaseLock(RLock lock, boolean isLocked) {
        if (isLocked && lock.isHeldByCurrentThread()) {
            try {
                lock.unlock();
                log.debug("分布式锁 [{}] 已释放", lock.getName());
            } catch (IllegalMonitorStateException e) {
                log.warn("释放分布式锁 [{}] 失败，锁可能已过期", lock.getName(), e);
            }
        }
    }

    /**
     * 内部查询方法，封装了共同的查询逻辑
     */
    private List<InpBillDetailDo> listItemsInternal(InpBillRequest data) {
        LambdaQueryWrapper<InpBillDetailDo> queryWrapper = Wrappers.lambdaQuery();
        if (data != null) {
            if (StringUtils.isNotBlank(data.getPatientId())) {
                queryWrapper.eq(InpBillDetailDo::getPatientId, data.getPatientId());
            }
            if (data.getVisitId() != null) {
                queryWrapper.eq(InpBillDetailDo::getVisitId, data.getVisitId());
            }
            if (StringUtils.isNotBlank(data.getItemClass())) {
                queryWrapper.eq(InpBillDetailDo::getItemClass, data.getItemClass());
            }
            if (StringUtils.isNotBlank(data.getItemCode())){
                queryWrapper.eq(InpBillDetailDo::getItemCode,data.getItemCode());
            }
            if (StringUtils.isNotBlank(data.getItemName())){
                queryWrapper.like(InpBillDetailDo::getItemName,data.getItemName());
            }
            if (!ObjectUtils.isEmpty(data.getStartDate())){
                queryWrapper.ge(InpBillDetailDo::getBillingDateTime,data.getStartDate());
            }
            if (!ObjectUtils.isEmpty(data.getEndDate())){
                queryWrapper.le(InpBillDetailDo::getBillingDateTime,data.getEndDate());
            }
            queryWrapper.orderByAsc(InpBillDetailDo::getItemClass, InpBillDetailDo::getItemCode, InpBillDetailDo::getItemSpec, InpBillDetailDo::getBillingDateTime);
        }
        return baseMapper.selectList(queryWrapper);
    }
}